package com.galaxis.wms.security.authentication;

import com.galaxis.wms.EnumConstant;
import com.galaxis.wms.entity.BasLoginAgent;
import com.galaxis.wms.entity.BasUser;
import com.galaxis.wms.entity.SysJwtToken;
import com.galaxis.wms.security.utils.SecurityContextExtUtils;
import com.yvan.core.DbConv;
import com.yvan.core.SystemClock;
import com.yvan.security.SecurityDataSource;
import com.yvan.security.authentication.token.VerifyJwtToken;
import com.yvan.security.config.RedisConfig;
import com.yvan.security.config.SecurityConfig;
import com.yvan.security.exception.AuthenticationException;
import com.yvan.security.exception.InvalidJwtTokenException;
import com.yvan.security.exception.LoginNameNotFoundException;
import com.yvan.security.exception.UserDisabledException;
import com.yvan.security.utils.SecurityRedisKey;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.Objects;

import static com.galaxis.wms.query.QBasLoginAgent.basLoginAgent;
import static com.galaxis.wms.query.QBasUser.basUser;
import static com.galaxis.wms.query.QSysJwtToken.sysJwtToken;

/**
 * 作者：lizw <br/>
 * 创建时间：2020/12/14 21:22 <br/>
 */
@Slf4j
public class DefaultVerifyJwtToken implements VerifyJwtToken {
    @Override
    public void verify(String jwtToken, String userId, Claims claims, SecurityConfig securityConfig, HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        final RedisConfig redis = securityConfig.getRedis();
        // 校验JWT-Token
        // verifyJwtToken(redis, DbConv.asLong(userId), Long.parseLong(claims.getId()));
        // 判断账号是否可用
        verifyUser(redis, SecurityContextExtUtils.getLoginId(), DbConv.asLong(userId));
        log.debug("### JWT-Token验证成功 -> userId={} | Token={}", claims.getSubject(), jwtToken);
    }

    protected void verifyJwtToken(RedisConfig redis, Long userId, Long id) {
        // 验证JWT-Token状态
        SysJwtToken jwtToken = null;
        // 从Redis缓存读取SysJwtToken
        if (redis.isEnable()) {
            jwtToken = SecurityDataSource.getRedis().vGet(
                    SecurityRedisKey.getTokenKey(redis.getRedisNamespace(), DbConv.asString(userId),
                            String.valueOf(id)),
                    SysJwtToken.class
            );
        }
        // 从数据库中读取SysJwtToken
        if (jwtToken == null) {
            jwtToken = SecurityDataSource.getQueryDSL().selectFrom(sysJwtToken)
                    .where(sysJwtToken.id.eq(id))
                    .fetchOne();
            // SysJwtToken缓存在Redis中
            if (redis.isEnable() && jwtToken != null) {
                SecurityDataSource.getRedis().vSet(
                        SecurityRedisKey.getTokenKey(redis.getRedisNamespace(), DbConv.asString(userId),
                                String.valueOf(jwtToken.getId())),
                        jwtToken,
                        jwtToken.getExpiredTime().getTime() - SystemClock.now()
                );
            }
        }
        if (jwtToken == null) {
            // 无效的 Token
            throw new InvalidJwtTokenException("无效的Token");
        }
        Date now = new Date();
        if (jwtToken.getExpiredTime() != null && now.compareTo(jwtToken.getExpiredTime()) >= 0) {
            // 已过期
            throw new InvalidJwtTokenException("Token已过期");
        }
        if (!Objects.equals(jwtToken.getDisable(), EnumConstant.DISABLE_0)) {
            // 已禁用
            String msg = "Token已禁用";
            if (jwtToken.getDisableReason() != null) {
                switch (jwtToken.getDisableReason()) {
                    case EnumConstant.JWT_TOKEN_DISABLE_REASON_0:
                        msg = "Token已失效(使用refresh token)";
                        break;
                    case EnumConstant.JWT_TOKEN_DISABLE_REASON_1:
                        msg = "Token已禁用";
                        break;
                    case EnumConstant.JWT_TOKEN_DISABLE_REASON_2:
                        msg = "Token已失效(被挤下线)";
                        break;
                    case EnumConstant.JWT_TOKEN_DISABLE_REASON_3:
                        msg = "Token已失效(已登出)";
                        break;
                }
            }
            throw new InvalidJwtTokenException(msg);
        }
    }

    protected void verifyUser(RedisConfig redis, Long loginId, Long userId) {
        final long timeout = 1000 * 60 * 30;
        // 获取 SysLoginAgent
        BasLoginAgent loginAgent = null;
        if (redis.isEnable()) {
            loginAgent = SecurityDataSource.getRedis().vGet(
                    SecurityRedisKey.getLoginAgentKey(redis.getRedisNamespace(), loginId),
                    BasLoginAgent.class
            );
        }
        if (loginAgent == null) {
            loginAgent = SecurityDataSource.getQueryDSL().selectFrom(basLoginAgent)
                    .where(basLoginAgent.loginId.eq(loginId))
                    .fetchOne();
            if (redis.isEnable() && loginAgent != null) {
                SecurityDataSource.getRedis().vSet(
                        SecurityRedisKey.getLoginAgentKey(redis.getRedisNamespace(), loginId),
                        loginAgent,
                        timeout
                );
            }
        }
        if (loginAgent == null) {
            throw new LoginNameNotFoundException("登录名不存在");
        }
        if (!Objects.equals(loginAgent.getIsEnable(), EnumConstant.ENABLED_1)) {
            throw new UserDisabledException("登录账号被禁用");
        }
        // 获取 SysUser
        BasUser user = null;
        if (redis.isEnable()) {
            user = SecurityDataSource.getRedis().vGet(
                    SecurityRedisKey.getUserKey(redis.getRedisNamespace(), DbConv.asString(userId)),
                    BasUser.class
            );
        }
        if (user == null) {
            user = SecurityDataSource.getQueryDSL().selectFrom(basUser)
                    .where(basUser.userId.eq(userId))
                    .fetchOne();
            if (redis.isEnable() && user != null) {
                SecurityDataSource.getRedis().vSet(
                        SecurityRedisKey.getUserKey(redis.getRedisNamespace(), DbConv.asString(userId)),
                        user,
                        timeout
                );
            }
        }
        if (user == null) {
            throw new LoginNameNotFoundException("登录名不存在");
        }
        if (!Objects.equals(user.getIsEnable(), EnumConstant.ENABLED_1)) {
            throw new UserDisabledException("登录账号被禁用");
        }
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}
